Skip to content

feat(BOP-495): ERC-8056 interface-review follow-ups (renames + Conversion extension) - #192

Merged
robriks merged 12 commits into
mainfrom
markusosterlund/bop-495-schedule-multiplier-updates-interface-review-follow-ups
Aug 10, 2026
Merged

feat(BOP-495): ERC-8056 interface-review follow-ups (renames + Conversion extension)#192
robriks merged 12 commits into
mainfrom
markusosterlund/bop-495-schedule-multiplier-updates-interface-review-follow-ups

Conversation

@robriks

@robriks robriks commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

Summary

Applies the Aug 4 2026 B20 Interface Review follow-ups to the ERC-8056 scaled-multiplier surface (Solidity interface + reference mock + tests + smoke). Paired in lockstep with base/base PR base/base#4285 — land together.

Scope is ERC-8056 + multiplier scheduling only. Every wire change is Cobalt-only (AssetV2, not yet activated on any network) or add-alias + deprecate on the frozen Beryl surface — nothing on-chain breaks.

Changes

  • Rename IScaledUIAmount.solIERC8056.sol (file only; interface identifiers unchanged).
  • Cobalt-only vocabulary: ScheduleOverlapPendingUpdateExists, NoScheduledMultiplierNoScheduledUIMultiplier, MultiplierUpdateCancelledUIMultiplierUpdateCancelled.
  • updateUIMultiplier is the canonical instant-failsafe.
  • IScaledUIAmountConversion (0x57854fc3) adopted: toUIAmount / fromUIAmount are the canonical converters, advertised via supportsInterface.
  • MAX_UI_MULTIPLIER() getter exposes the type(uint128).max setter bound.
  • Dual event on the instant setter: updateUIMultiplier (and the retained updateMultiplier) emits both the deprecated MultiplierUpdated(newMultiplier) and the ERC-8056 UIMultiplierUpdated, so indexers on the legacy topic keep working. The scheduled setUIMultiplier emits only UIMultiplierUpdated.

Deprecation model (keep in interface, marked deprecated)

Following the team decision (and mirroring #193's burnBlocked treatment), the legacy methods updateMultiplier / toScaledBalance / toRawBalance are kept in the IB20Asset interface, documented DEPRECATED. — not removed. They remain dialable and aliased under the new names, so block explorers (which need the advertised legacy surface) and developers (who get the canonical names) are both satisfied.

Test plan

Refs

BOP-495 (parent BOP-429 / B20 Improvements). Paired base/base PR: base/base#4285.

robriks and others added 6 commits August 4, 2026 16:34
Rename the ERC-8056 interface file to IERC8056.sol per the Aug 4 2026 B20
interface review. File rename only: the three interface identifiers
(IScaledUIAmount, IScaledUIAmountNewUIMultiplier, IScaledUIAmountBalances)
and every wire symbol are unchanged. Updates all import paths and one doc
comment reference.

Co-authored-by: Cursor <cursoragent@cursor.com>
…ors/events

Per the Aug 4 2026 B20 interface review, rename the Cobalt-only ERC-8056
scheduled-multiplier symbols to the "UI Multiplier" vocabulary:

  error ScheduleOverlap        -> PendingUpdateExists
  error NoScheduledMultiplier  -> NoScheduledUIMultiplier
  event MultiplierUpdateCancelled -> UIMultiplierUpdateCancelled

Updates the IB20Asset interface, the MockB20Asset reference implementation, the
affected unit tests, the scheduled-multiplier smoke journey, and the docs. These
symbols are Cobalt-only (AssetV2) and not yet live, so the rename is safe.

Co-authored-by: Cursor <cursoragent@cursor.com>
Advertise the canonical instant-failsafe under the ERC-8056 "UI Multiplier"
vocabulary as `updateUIMultiplier` and de-advertise the legacy `updateMultiplier`
from IB20Asset. The MockB20Asset reference keeps `updateMultiplier` dialable
(delegating to the shared body) so the precompile's retained legacy selector
stays cross-validated by the fork tests; a regression test pins that the legacy
selector remains callable and still emits UIMultiplierUpdated.

B20FactoryLib gains encodeUpdateUIMultiplier (canonical) and keeps
encodeUpdateMultiplier (legacy, encoded by signature). Docs and smoke journeys
are swept separately in the reconciliation commit.

Co-authored-by: Cursor <cursoragent@cursor.com>
Add the ERC-8056 Conversion extension interface (id 0x57854fc3) to IERC8056.sol,
inherit it on IB20Asset, and advertise it via supportsInterface. The
MockB20Asset reference implements toUIAmount/fromUIAmount and keeps the legacy
toScaledBalance/toRawBalance dialable (shared bodies) so the precompile's
retained legacy selectors stay cross-validated; a regression test pins that they
remain callable and agree with the canonical converters.

toScaledBalance/toRawBalance are de-advertised from IB20Asset; the dedicated
tests move to toUIAmount/fromUIAmount, and B20FactoryLib/docs are updated. The
"Conversion extension not claimed" doc statement is flipped. Remaining doc/smoke
naming is swept in the reconciliation commit.

Co-authored-by: Cursor <cursoragent@cursor.com>
Advertise MAX_UI_MULTIPLIER() (== type(uint128).max) on IB20Asset. The
MockB20Asset reference declares it as a public constant (single source of truth,
auto-generated getter) and the setter guards reference it, so callers can read
the accepted-multiplier bound without hitting the InvalidMultiplier revert path.
Pinned to the uint128 overflow guard by a constants test.

Co-authored-by: Cursor <cursoragent@cursor.com>
Sweep the docs and smoke journeys for the renamed/added surface and document the
event reconciliation:

- docs/B20/Asset.md: use updateUIMultiplier / toUIAmount / fromUIAmount as the
  canonical names (noting the retained-but-de-advertised legacy selectors),
  expose MAX_UI_MULTIPLIER(), and spell out the intended double event — an
  instant updateUIMultiplier that supersedes a *live* schedule emits
  UIMultiplierUpdateCancelled then UIMultiplierUpdated{block.timestamp}, and
  AssetV2 never emits the legacy V1 MultiplierUpdated (not part of IB20Asset).
- scheduled_multiplier.py (Cobalt-only): switch to the canonical
  updateUIMultiplier / toUIAmount.
- asset_lifecycle.py (cross-fork): keep the legacy updateMultiplier /
  toScaledBalance / toRawBalance selectors — the only multiplier/conversion
  selectors dialable on both Beryl and Cobalt — with a comment explaining why.
  abis.py appends their fragments since IB20Asset no longer advertises them.

Co-authored-by: Cursor <cursoragent@cursor.com>
@linear

linear Bot commented Aug 4, 2026

Copy link
Copy Markdown

BOP-495

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown

Interface Coverage

✅ All interface functions have test coverage.

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown

📊 Forge Coverage (src/lib/)

🟡 ≥95% across all metrics — some metrics below 99%.

File Lines Stmts Branches Funcs
🟡 B20FactoryLib.sol 97.70% 98.00% 100.00% 95.00%
🔴 test/lib/ForceFeeder.sol 0.00% 0.00% 100.00% 0.00%
🔴 test/lib/PrecompileProbe.sol 0.00% 0.00% 0.00% 0.00%
🟢 MockActivationRegistry.sol 100.00% 100.00% 100.00% 100.00%
🟢 MockActivationRegistryStorage.sol 100.00% 100.00% 100.00% 100.00%
🟢 MockB20.sol 100.00% 100.00% 100.00% 100.00%
🟢 MockB20Asset.sol 100.00% 100.00% 100.00% 100.00%
🟡 MockB20Factory.sol 98.96% 99.10% 100.00% 100.00%
🟢 MockB20Stablecoin.sol 100.00% 100.00% 100.00% 100.00%
🟢 MockB20Storage.sol 100.00% 100.00% 100.00% 100.00%
🟡 MockPolicyRegistry.sol 100.00% 99.54% 97.67% 100.00%
🟢 MockPolicyRegistryStorage.sol 100.00% 100.00% 100.00% 100.00%
Total 97.06% 97.51% 98.14% 97.00%

Full report: download artifact. To browse locally: make coverage (runs forge coverage + genhtml + opens the HTML report).

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown

⚠️ Fork tests: 84 failed, 673 passed

These failures indicate divergences where base/base needs to catch up to the base-std spec. This check is advisory and does not block merging.

Failing tests
  • test_cancelUIMultiplierUpdate_revert_matured(uint256): custom error 0x628e600f; counterexample: calldata=0xea4532b3011fda6abd2245cdaf3eff7c75410c1d72b13d9af494e9389176ea7c58c4390d args=[508592565967465447386355079426021318754964389842043475132556151387869952269 [5.085e74]]
  • test_cancelUIMultiplierUpdate_revert_unauthorized(address): custom error 0x628e600f; counterexample: calldata=0x22f9091c00000000000000000000000095b5962853a16bc66ae2a1ebd4d015e8be13b479 args=[0x95B5962853A16BC66ae2a1EBd4D015e8bE13B479]
  • test_cancelUIMultiplierUpdate_success_clearsPending(uint256,uint256): custom error 0x628e600f; counterexample: calldata=0x8e8d77880000000000000000000000006e1da34998c93c1ee566c8b920debace834ce4d000000000000000000000000000000000000000000ada1ffb5d0b310a5cffd3ee args=[628649930747686924305751728043520261730547328208 [6.286e47], 3358546957087677698283656174 [3.358e27]]
  • test_cancelUIMultiplierUpdate_success_emitsEvent(uint256,uint256): custom error 0x628e600f; counterexample: calldata=0xd24efe3300000000000000000000000000000000000000000000000000000000000000000649c476698282e34e6fa104cfdfc0d5c9266d187053b30655abe40c72a6c7c4 args=[0, 2844212861897577912659896429815553841336825188818582866796187286938904348612 [2.844e75]]
  • test_cancelUIMultiplierUpdate_success_leavesCurrentUntouched(uint256): custom error 0x628e600f; counterexample: calldata=0xaf914373000000000000000000000000000000000006c5a4dd96a1a2548722c9f4bd4da2 args=[35162477840070023122677717727530402 [3.516e34]]
  • test_conversion_deprecated_stillDialable(uint256): custom error 0x3248d4ff; counterexample: calldata=0x5f1549560000000000000000000000000000000656fe18fa8eeed44e666c20665b13665c args=[2157327159194308106601416368092328715868 [2.157e39]]
  • test_erc8056Surface_success_aliasesResolve(uint256): custom error 0x3248d4ff; counterexample: calldata=0x91648cee0000000000000000000000000000000000000000000000000000000000003a4d args=[14925 [1.492e4]]
  • test_fromUIAmount_success_explicitZeroMultiplierFallsBackToWad(uint256): custom error 0x65cd9b3c; counterexample: calldata=0x2e5d48fc000000000000004735121ed3ffa306e3891a95c2f8c55446e6c2502691eda4da args=[446975515022760930555477002736983510580624346410830288233690 [4.469e59]]
  • test_fromUIAmount_success_identityOnWadDefault(uint256): custom error 0x65cd9b3c; counterexample: calldata=0x14325055fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc args=[115792089237316195423570985008687907853269984665640564039457584007913129639932 [1.157e77]]
  • test_fromUIAmount_success_invertsByStoredMultiplier(uint256,uint256): custom error 0x65cd9b3c; counterexample: calldata=0xe11ab12f0000000000000000000000007109709ecfa91a80626ff3989d68f67f5b1dd12e0000000000000000000000000000000000000000000000000000000000000722 args=[645326474426547203313410069153905908525362434350 [6.453e47], 1826]
  • test_fromUIAmount_success_roundTripExactOnWadDefault(uint256): custom error 0x3248d4ff; counterexample: calldata=0x76c2c36b0000000000000000000000000000000000000000000000000000000000002b6e args=[11118 [1.111e4]]
  • test_fromUIAmount_success_roundTripFloors(uint256,uint256): custom error 0x3248d4ff; counterexample: calldata=0x3f088b7a000000000000000000000000000000000000000000000000e35de38f46431f270000000000000000000000000000000000000000000000000000052081dbd4ee args=[16383501223942364967 [1.638e19], 5637175760110 [5.637e12]]
  • test_fromUIAmount_success_zeroUIAmount(uint256): custom error 0x65cd9b3c; counterexample: calldata=0x1da82273000000004b72c2f107f453f1615b227be2f30a249800224903ef8386dfb2609c args=[7945632013982589311188461360605340022677561217618779675868160745628 [7.945e66]]
  • test_multiplier_success_renamedFromShareRatio(uint256): custom error 0x3248d4ff; counterexample: calldata=0x94be8dcc0000000086ef5ac2229e542367afc3321622b0d8bec6f38033614d60d622bff1 args=[14210311823917167500253448424486179589374338532005037798594843951089 [1.421e67]]
  • test_newUIMultiplier_success_maturedMirrorsUiMultiplier(uint256): custom error 0x628e600f; counterexample: calldata=0x42f477220000000000000000000000000000000000000000000000000000000000002114 args=[8468]
  • test_newUIMultiplier_success_reportsLivePending(uint256,uint256): custom error 0x628e600f; counterexample: calldata=0xf706898000000000007188a59e02fd4bb6f12bbe5f389abd88f0c8d5f39d52ffd6c2304c00000000000000000000cf86788aaa6925762a080a479718431705528284871e args=[46705086914564690617336947423479559682331035986396807521793880140 [4.67e64], 77644424156081752742363173959717224515756663222077214 [7.764e52]]
  • test_pendingSlot_success_decodesScheduledPending(uint256,uint256): custom error 0x628e600f; counterexample: calldata=0x241843700000000000000000000000000000000000000000000000000000000000000041265b220c5a8891efdd9e1b1b7fa72f257bd5169f8d87e319cf3dad6ff52b94ad args=[65, 17348906321488394533951696364807228209147389490132821613036366320581948970157 [1.734e76]]
  • test_scheduledSetter_success_renamedFromSetUIMultiplier(uint256): custom error 0x628e600f; counterexample: calldata=0x1e36717a000000000000000000000000000000000000000000000000000000000004ddb8 args=[318904 [3.189e5]]
  • test_toUIAmount_success_explicitZeroMultiplierFallsBackToWad(uint256): custom error 0x3248d4ff; counterexample: calldata=0x800dbd6f00000000000000000000000000000000000000000000000000000000151a38e9 args=[354040041 [3.54e8]]
  • test_toUIAmount_success_identityOnWadDefault(uint256): custom error 0x3248d4ff; counterexample: calldata=0x199e403a0000000000000000000000000000000000000000000000000000000000001411 args=[5137]
  • test_toUIAmount_success_scalesByStoredMultiplier(uint256,uint256): custom error 0x3248d4ff; counterexample: calldata=0xa378d219000000000000000000000000000000000000000000000000000003301e22eaac0000000000000000000000000000000000000000000000000000000000000001 args=[3505198918316 [3.505e12], 1]
  • test_toUIAmount_success_zeroRawAmount(uint256): custom error 0x3248d4ff; counterexample: calldata=0x3f144b340000000000000000000000000000000000000000000000000000000000000042 args=[66]
  • test_updateMultiplier_deprecated_stillDialable(uint256): deprecated updateMultiplier must also emit MultiplierUpdated: -1 <= -1; counterexample: calldata=0x8e38ffd6fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe args=[115792089237316195423570985008687907853269984665640564039457584007913129639934 [1.157e77]]
  • test_updateMultiplier_success_emitsEvent(uint256): log != expected log; counterexample: calldata=0xfda1a8f90000000000000000000000000000000000000000000000000000000000003234 args=[12852 [1.285e4]]
  • test_updateUIMultiplier_revert_aboveUint128Ceiling(uint256): Error != expected error: custom error 0x628e600f != InvalidMultiplier(); counterexample: calldata=0x83cbf42d0000000000000000000000000000000000000000000000000000000000016582 args=[91522 [9.152e4]]
  • test_updateUIMultiplier_revert_effectiveAtInPast(uint256): Error != expected error: custom error 0x628e600f != EffectiveAtInPast(37); counterexample: calldata=0x2e69d3320000000000000000000000000000000000000000000000000000000000000025 args=[37]
  • test_updateUIMultiplier_revert_effectiveAtTooFar(uint256): Error != expected error: custom error 0x628e600f != EffectiveAtTooFar(1978244179088844717721110478595586298723770320555360369 [1.978e54]); counterexample: calldata=0xfa43acdc00000000000000000014a7619846ef427986c7d36bc7a13bce931afdef544071 args=[1978244179088844717721110478595586298723770320555360369 [1.978e54]]
  • test_updateUIMultiplier_revert_metadataRoleInsufficient(uint256): Error != expected error: custom error 0x628e600f != AccessControlUnauthorizedAccount(0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e, 0x97667070c54ef182b0f5858b034beac1b6f3089aa2d3188bb1e8929f4fa9b929); counterexample: calldata=0x8e55eaf30000000000000000000000000000000000000000000000000000000000001900 args=[6400]
  • test_updateUIMultiplier_revert_pendingUpdateExists(uint256,uint256): custom error 0x628e600f; counterexample: calldata=0x118ebc9f000000000000000000000000000000000000000000000000000000004772e663000000000000000000000000000000000000000000000004716721c0c5c8cdea args=[1198712419 [1.198e9], 81958513455560969706 [8.195e19]]
  • test_updateUIMultiplier_revert_unauthorized(address,uint256): Error != expected error: custom error 0x628e600f != AccessControlUnauthorizedAccount(0xCB00000000000000000000000000000000000000, 0x97667070c54ef182b0f5858b034beac1b6f3089aa2d3188bb1e8929f4fa9b929); counterexample: calldata=0x3b75c884000000000000000000000000cb000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001010 args=[0xCB00000000000000000000000000000000000000, 4112]
  • test_updateUIMultiplier_success_emitsEvent(uint256,uint256): log != expected log; counterexample: calldata=0x6fb2ac3a0000000000000000000000000000000000000000000000000000000000000178000000000000000000000000000000000000000000000000000000004e487b71 args=[376, 1313373041 [1.313e9]]
  • test_updateUIMultiplier_success_lazyFlipAtBoundary(uint256): custom error 0x628e600f; counterexample: calldata=0xd55bc7fe00000000000000000000000000000000000000000000000000000000000014b4 args=[5300]
    [FAIL: Error != expected error: InternalCallFailed(0x3248d4ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff) != panic: arithmetic underflow or overflow (0x11)] test_announce_innerPanic_propagatesRaw() (gas: 102160)
    [FAIL: Error != expected error: custom error 0x2c97a0f0 != UIMultiplierUpdateDoesNotExist()] test_cancelUIMultiplierUpdate_revert_noPending() (gas: 45690)
    [FAIL: Error != expected error: custom error 0x628e600f != InvalidMultiplier()] test_updateUIMultiplier_revert_zeroMultiplier() (gas: 45884)
    [FAIL: custom error 0x628e600f] test_b20AssetLayout_success_populatedSnapshotMatchesAllSlots() (gas: 74122)
    [FAIL: custom error 0x628e600f] test_reorder_success_cancelThenScheduleInOneBracket() (gas: 45458)
    [FAIL: custom error 0x628e600f] test_updateMultiplier_success_clearsLivePending() (gas: 45462)
    [FAIL: custom error 0x628e600f] test_updateMultiplier_success_clearsMaturedPendingNoCancelEvent() (gas: 45463)
    [FAIL: custom error 0x628e600f] test_updateUIMultiplier_success_materializesMaturedPending() (gas: 45484)
    [FAIL: custom error 0x785c0cf0] test_maxUIMultiplier_success_equalsUint128Max() (gas: 5192)
    [FAIL: must advertise IScaledUIAmountConversion] test_supportsInterface_success_claimedIds() (gas: 11895)

robriks added a commit to base/base that referenced this pull request Aug 4, 2026
The Cobalt conformance leg patched current base/base (this PR's renamed/added
ERC-8056 surface) into base-anvil but ran the previously-pinned base-std
reference, which still expects the pre-rename symbols — a guaranteed mismatch.
Point the Cobalt base_std_ref at the paired base-std commit (base/base-std#192)
so the two repos are cross-validated in lockstep. Beryl (v1.0.0) is unchanged;
V1 is frozen and untouched by this PR.

Co-authored-by: Cursor <cursoragent@cursor.com>
Remove verbose comments in IB20Asset.sol, script/smoke/abis.py, and
script/smoke/journeys/asset_lifecycle.py per review feedback; the code is
self-explanatory.

Co-authored-by: Cursor <cursoragent@cursor.com>
robriks added a commit to base/base that referenced this pull request Aug 4, 2026
Follow the base-std PR head after dropping review comments (base/base-std#192);
keeps the conformance pair in lockstep.

Co-authored-by: Cursor <cursoragent@cursor.com>
…MultiplierUpdated

Per reviewer intent, the instant multiplier setter (updateUIMultiplier and the
retained updateMultiplier) now emits the deprecated MultiplierUpdated(newMultiplier)
alongside the ERC-8056 UIMultiplierUpdated, so indexers on the legacy topic keep
working. The scheduled setUIMultiplier still emits only UIMultiplierUpdated.

Re-declares MultiplierUpdated on IB20Asset, emits it from MockB20Asset's shared
instant-setter body (cancel -> MultiplierUpdated -> UIMultiplierUpdated), and
updates the affected unit/regression tests, smoke journeys, and docs.

Co-authored-by: Cursor <cursoragent@cursor.com>
@robriks robriks self-assigned this Aug 5, 2026
@robriks

robriks commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator Author

(fork tests need the names in base/base #4285 to land before passing)

… not removed

Per review: match the agreed deprecation pattern (mirroring #193's
burnBlocked) — retain the legacy functions in the interface, marked DEPRECATED,
rather than removing them. Removing them broke the block-explorer customer class.

Re-declares updateMultiplier / toScaledBalance / toRawBalance on IB20Asset with
`@notice DEPRECATED.` docs pointing at updateUIMultiplier / toUIAmount /
fromUIAmount. Reverts the workarounds the removal forced: drops the manual
_DEPRECATED_ASSET_SELECTORS ABI fragments in smoke (the selectors are back in the
compiled ABI), restores abi.encodeCall in B20FactoryLib.encodeUpdateMultiplier,
and switches the deprecation regression tests to typed calls. Docs/mocks updated.

Co-authored-by: Cursor <cursoragent@cursor.com>
@stevieraykatz

stevieraykatz commented Aug 5, 2026

Copy link
Copy Markdown
Member

PR Body still describes the previous approach of dropping deprecated methods. Also your local env is leaking into the PR body via ~/work/erc8056... in the Refs section.

Comment thread script/smoke/README.md Outdated
Address review feedback (#192): the smoke journeys now assert the
instant setter emits the deprecated MultiplierUpdated alongside UIMultiplierUpdated
(dual event), so correct the README rows — the `multiplier` journey exercises
updateUIMultiplier and expects UIMultiplierUpdated + UIMultiplierUpdateCancelled +
MultiplierUpdated; the `asset` rebase emits both events on Cobalt. Also drops a
stale example from the assert_no_log docstring.

Co-authored-by: Cursor <cursoragent@cursor.com>

@ilikesymmetry ilikesymmetry left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm having second thoughts now on if our setter and cancellation system should be proactively designed for queueing multiple updates. I recall this is a somewhat known use case. If we know we're eventually going to have to build such a thing, I'd prefer to spend an extra cycle to get it right now so we don't have as much to change. Probably worth a longer conversation though.

Comment thread src/interfaces/IB20Asset.sol Outdated
error InvalidMetadataKey();

/// @notice A multiplier setter (`setUIMultiplier` or `updateMultiplier`) was called with a
/// @notice A multiplier setter (`setUIMultiplier` or `updateUIMultiplier`) was called with a

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we only have one setter right?

Suggested change
/// @notice A multiplier setter (`setUIMultiplier` or `updateUIMultiplier`) was called with a
/// @notice `updateUIMultiplier` was called with a

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there are two, the instantaneous failsafe (legacy) updateMultiplier and the newer updateUIMultiplier

Comment thread src/interfaces/IB20Asset.sol Outdated
Comment thread src/interfaces/IB20Asset.sol Outdated
Comment thread src/interfaces/IB20Asset.sol Outdated
/// @dev Reverts with `InvalidMultiplier` when `newMultiplier` is zero or above `type(uint128).max`.
///
/// @param newMultiplier New multiplier scaled to `WAD_PRECISION`; must be in `(0, type(uint128).max]`.
function updateUIMultiplier(uint256 newMultiplier) external;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

confused, don't we have a setUIMultiplierUpdate that we just need to rename? Whatever function we are adding in Cobalt needs to have the effectiveAt parameter

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we (b20 + tequities) had decided to keep the instantaneous updateMultiplier function as an admin failsafe, partly because its "tech debt" can't be aliased under the new function

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(as opposed to the new updateUIMultiplier that accepts the effectiveAt parameter)

Comment thread src/interfaces/IB20Asset.sol Outdated
Comment thread src/interfaces/IB20Asset.sol Outdated
…ultiplier vocabulary for cancel + errors

Addresses ilikesymmetry's review on #192. The canonical Cobalt setter is now the
scheduled `updateUIMultiplier(newMultiplier, effectiveAt)` (rename of `setUIMultiplier`),
rather than an added instant alias. The instant path stays as the deprecated
`updateMultiplier` (retained, dialable). Also, for "UI multiplier update" vocabulary parity:

- cancelScheduledMultiplier -> cancelUIMultiplierUpdate
- PendingUpdateExists(effectiveAt) -> UIMultiplierUpdateExists(effectiveAt)
- NoScheduledUIMultiplier -> UIMultiplierUpdateDoesNotExist

The redundant instant `updateUIMultiplier(uint256)` alias is dropped. Interface, mock,
factory encoders, unit/regression/storage tests, smoke journeys and docs updated in lockstep.

Co-authored-by: Cursor <cursoragent@cursor.com>
@robriks

robriks commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator Author

I'm having second thoughts now on if our setter and cancellation system should be proactively designed for queueing multiple updates. I recall this is a somewhat known use case. If we know we're eventually going to have to build such a thing, I'd prefer to spend an extra cycle to get it right now so we don't have as much to change. Probably worth a longer conversation though.

this was litigated in a conversation with the tequities team around 3 weeks ago, and the current design leaves room for future extension.

ofc we can reverse that decision but I toward that being overengineering for a problem that we have not faced yet. it would also bring into question the value of the ongoing audit since a refactor of that size touches more than just the cosmetic naming we're currently refactoring

…95-schedule-multiplier-updates-interface-review-follow-ups
@robriks robriks closed this Aug 10, 2026
@robriks robriks reopened this Aug 10, 2026
@robriks
robriks merged commit 04d645a into main Aug 10, 2026
9 of 10 checks passed
@robriks
robriks deleted the markusosterlund/bop-495-schedule-multiplier-updates-interface-review-follow-ups branch August 10, 2026 19:34
crazywriter1 pushed a commit to crazywriter1/base that referenced this pull request Aug 11, 2026
…sion extension) (base#4285)

* refactor(BOP-495): adopt UI-multiplier vocabulary for Cobalt-only errors/events

Per the Aug 4 2026 B20 interface review, rename the Cobalt-only ERC-8056
scheduled-multiplier symbols to the "UI Multiplier" vocabulary:

  error ScheduleOverlap        -> PendingUpdateExists
  error NoScheduledMultiplier  -> NoScheduledUIMultiplier
  event MultiplierUpdateCancelled -> UIMultiplierUpdateCancelled

These symbols were introduced at Cobalt (AssetV2) and are not yet activated on
any network (Cobalt is unscheduled on mainnet/sepolia), so a pure rename of the
wire surface is consensus-safe. The frozen Beryl (V1) surface is untouched.
Regenerates V2_ABI_FINGERPRINT (V1 fingerprint and the shared-signature pins are
unchanged).

Co-authored-by: Cursor <cursoragent@cursor.com>

* feat(BOP-495): add updateUIMultiplier alias; deprecate updateMultiplier

Add the canonical ERC-8056-vocabulary instant-failsafe selector
`updateUIMultiplier(uint256)` at Cobalt (AssetV2), routing to the same
`Asset::update_multiplier` logic (identical behavior and events).

The Beryl-live `updateMultiplier` selector is FROZEN, so this is add-alias +
deprecate (not rename): the legacy selector stays dialable in the precompile
(the V2 selector set is intentionally, permanently larger) and only base-std's
advertised interface drops it. Regenerates V2_ABI_FINGERPRINT and the frozen
selector set (20 -> 21); the v1-subset delta grows 8 -> 9. V1 fingerprint and
the frozen Beryl surface are untouched.

Co-authored-by: Cursor <cursoragent@cursor.com>

* feat(BOP-495): adopt IScaledUIAmountConversion (toUIAmount/fromUIAmount)

Reverse the prior opt-out and claim the ERC-8056 Conversion extension
(0x57854fc3) at Cobalt. Add `toUIAmount(uint256)` (raw->UI) and
`fromUIAmount(uint256)` (UI->raw) selectors routing to the existing
`to_scaled_balance` / `to_raw_balance` logic (identical behavior, effective
multiplier). Advertise 0x57854fc3 in ERC8056_INTERFACE_IDS (supports_interface
already returns any advertised id), add the XOR pin, and invert the
"conversion extension not claimed" test.

Names/id cross-checked against the live ERC-8056 spec; the XOR pin confirms
0x57854fc3 == toUIAmount ^ fromUIAmount. The Beryl-live toScaledBalance /
toRawBalance selectors are frozen, so this is add-alias + deprecate: they stay
dialable in the precompile and only base-std de-advertises them. Regenerates
V2_ABI_FINGERPRINT and the frozen selector set (21 -> 23); v1-subset delta 9 -> 11.
Rationale for the reversal: maximize ERC-8056 conformance and explorer/builder
DevX; legacy names remain dialable so nothing on-chain breaks.

Co-authored-by: Cursor <cursoragent@cursor.com>

* feat(BOP-495): expose MAX_UI_MULTIPLIER() view

Add a Cobalt-only MAX_UI_MULTIPLIER() getter returning type(uint128).max, the
overflow guard the multiplier setters enforce. Previously callers had to trigger
the InvalidMultiplier revert path to learn the bound. Introduce the constant on
AssetV2 as the single source of truth and have set_ui_multiplier /
update_multiplier reference it; expose it via a version-gated Asset trait read
(reject_frozen_selector! default, V2 override), matching the other V2-only reads.

Regenerates V2_ABI_FINGERPRINT and the frozen selector set (23 -> 24); v1-subset
delta 11 -> 12. WAD_PRECISION() already satisfied the "multiplier precision
getter" review ask and is unchanged.

Co-authored-by: Cursor <cursoragent@cursor.com>

* test(BOP-495): update Asset V2 golden suite for the interface-review surface

Merged origin/main brought in the Asset V2 golden suite (base#4270), which predated
this branch. Reconcile it with the BOP-495 renames/additions:

- Rename the Cobalt-only symbol references (PendingUpdateExists,
  NoScheduledUIMultiplier, UIMultiplierUpdateCancelled).
- Add golden tests for the new selectors: updateUIMultiplier (aliases
  updateMultiplier), toUIAmount/fromUIAmount (Conversion aliases), and
  MAX_UI_MULTIPLIER; wire them into the exhaustive op-coverage match.
- Flip supportsInterface(0x57854fc3) to advertised (Conversion extension now
  claimed).
- Re-bless ROOT_CANCEL_SCHEDULED_MULTIPLIER and
  ROOT_UPDATE_MULTIPLIER_CLEARS_PENDING: hash_token_state hashes emitted log
  topics, so renaming the cancel event's topic0 shifts those two roots. Storage
  behavior is unchanged; all other roots are identical.

Co-authored-by: Cursor <cursoragent@cursor.com>

* ci(BOP-495): pin Cobalt fork-test base-std to the paired BOP-495 commit

The Cobalt conformance leg patched current base/base (this PR's renamed/added
ERC-8056 surface) into base-anvil but ran the previously-pinned base-std
reference, which still expects the pre-rename symbols — a guaranteed mismatch.
Point the Cobalt base_std_ref at the paired base-std commit (base/base-std#192)
so the two repos are cross-validated in lockstep. Beryl (v1.0.0) is unchanged;
V1 is frozen and untouched by this PR.

Co-authored-by: Cursor <cursoragent@cursor.com>

* ci(BOP-495): re-pin Cobalt fork-test base-std to latest paired commit

Follow the base-std PR head after dropping review comments (base/base-std#192);
keeps the conformance pair in lockstep.

Co-authored-by: Cursor <cursoragent@cursor.com>

* feat(BOP-495): instant setter emits deprecated MultiplierUpdated + UIMultiplierUpdated

Per reviewer intent, AssetV2's instant setter (update_multiplier, dialed via
updateMultiplier / updateUIMultiplier) now emits the deprecated V1
MultiplierUpdated(newMultiplier) alongside the ERC-8056 UIMultiplierUpdated, for
backward compatibility with indexers on the legacy topic. The scheduled
set_ui_multiplier is unchanged (only UIMultiplierUpdated).

Event order on an instant override of a live schedule:
UIMultiplierUpdateCancelled -> MultiplierUpdated -> UIMultiplierUpdated. Updates
the abi doc + unit tests, and re-blesses the V2 golden roots for update_multiplier,
update_multiplier_clears_pending, and announce_v2 (the extra log topic shifts
those hashes; no storage-behavior change). Re-pins the Cobalt fork-test base_std_ref
to the paired base-std commit.

Co-authored-by: Cursor <cursoragent@cursor.com>

* ci(BOP-495): re-pin Cobalt base-std; sync abi docs to deprecation-in-interface

Point the Cobalt fork-test base_std_ref at the paired base-std commit (which now
retains the deprecated methods in IB20Asset rather than removing them). Update the
abi/v2.rs doc comments to say the legacy updateMultiplier / toScaledBalance /
toRawBalance are kept in base-std's interface as deprecated aliases (not removed),
and that updateMultiplier now emits both MultiplierUpdated and UIMultiplierUpdated.
Comment-only; V2_ABI_FINGERPRINT unchanged.

Co-authored-by: Cursor <cursoragent@cursor.com>

* ci(BOP-495): re-pin Cobalt base-std to 399ee93 (doc-only fix on the pair)

Co-authored-by: Cursor <cursoragent@cursor.com>

* refactor(BOP-495): make updateUIMultiplier the scheduled setter; UI-multiplier vocabulary for cancel + errors

Addresses ilikesymmetry's review on base-std base#192, applied to the Cobalt (AssetV2) wire
surface (Cobalt is not live, so these Cobalt-only selectors are free to rename):

- setUIMultiplier(newMultiplier, effectiveAt) -> updateUIMultiplier(newMultiplier, effectiveAt)
  (the canonical scheduled setter); the redundant instant updateUIMultiplier(uint256) alias is
  dropped. The instant path remains as the deprecated updateMultiplier(uint256).
- cancelScheduledMultiplier -> cancelUIMultiplierUpdate
- PendingUpdateExists(effectiveAt) -> UIMultiplierUpdateExists(effectiveAt)
- NoScheduledUIMultiplier -> UIMultiplierUpdateDoesNotExist

Selector set 24 -> 23; V2 ABI fingerprint and V1-subset delta (12 -> 11) regenerated. Golden
coverage, dispatch, labels and unit tests updated; storage/event roots are unchanged (renames
don't alter storage or emitted topics). Re-pins the Cobalt conformance gate to the paired
base-std commit 22d67a4.

Co-authored-by: Cursor <cursoragent@cursor.com>

* ci(BOP-495): re-pin Cobalt base-std to d0a3c16 (base-std in lockstep with main); tidy v1 golden doc

Bumps the Cobalt conformance pin to the base-std commit that merged base-std main, so the
paired SEIZE_RECEIVER_POLICY / seizeWithMemo changes (base-std base#188/base#193, base/base base#4252)
line up with the seize behavior this branch inherited from base/base main. Fixes the 4
seizeWithMemo Cobalt fork-test failures, which were a cross-repo skew, not a rename regression.

Also reflows one doc comment in the V1 golden negative test (no behavior change).

Co-authored-by: Cursor <cursoragent@cursor.com>

* test(BOP-495): assert all 11 V2-only selectors are excluded from the frozen V1 surface

The v1<>v2 freeze-guard only iterated the 8 scheduled-multiplier selectors; extend it to the
full 11-selector Cobalt delta by adding toUIAmount / fromUIAmount / MAX_UI_MULTIPLIER, matching
the delta pinned by v1_selectors_are_a_subset_of_v2 and golden_v2_selectors_unknown_at_v1.
Renamed asset_surface_excludes_scheduled_selectors -> asset_surface_excludes_v2_only_selectors.
Addresses the review-bot consistency finding. Test-only; no V1 behavior/wire change.

Co-authored-by: Cursor <cursoragent@cursor.com>

* test(BOP-495): assert MultiplierUpdated in matured-pending instant-setter test

update_multiplier_clears_matured_pending_without_cancel_event documented the dual-event
emission but only asserted UIMultiplierUpdated. Assert MultiplierUpdated at events[len-2] too,
mirroring update_multiplier_clears_live_pending_with_cancel_event, making the dual-event
contract explicit. Addresses the review-bot finding. Test-only.

Co-authored-by: Cursor <cursoragent@cursor.com>

* style(BOP-495): apply nightly rustfmt line-wrapping after the error/param renames

The UIMultiplierUpdateDoesNotExist / UIMultiplierUpdateExists { effectiveAt } renames shifted two
assert_eq! lines across the width boundary; nightly rustfmt (the CI toolchain, which applies the
unstable rustfmt.toml rules) rewraps them. No behavior change.

Co-authored-by: Cursor <cursoragent@cursor.com>

* ci(BOP-495): repin Cobalt base_std_ref to base-std main (base#192 merged)

base#192 squash-merged to base-std main as 04d645a. Move the Cobalt conformance pin off the
(now-orphaned) PR-branch commit d0a3c16 onto the stable base-std main SHA, per the immutable
(base-anvil SHA, base-std SHA) pair convention. Same paired ERC-8056 surface; the gate now
validates this base/base PR against base-std main.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants